package com.momoauth.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.momoauth.gateway.config.CommonConfig;
import com.momoauth.gateway.enumeration.CustomExceptionEnum;
import com.momoauth.gateway.config.GatewayConfig;
import com.momoauth.gateway.utils.IpUtil;
import com.momoauth.gateway.utils.JwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class GlobalAuthFilter implements GlobalFilter, Ordered {

    private static final String AUTHORIZE_TOKEN = "Authorization";

    @Resource
    GatewayConfig gatewayConfig;

    @Resource
    CommonConfig commonConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 从配置中心获取参数
        if (!gatewayConfig.getRestricted().isEmpty()) {
            Map<String,String> constraintMap = gatewayConfig.getRestricted();
            // 开启受限模式
            boolean isEnabled = Boolean.parseBoolean(constraintMap.get("enabled"));
            // 受限类型
            String restrictionType = constraintMap.get("restriction-type");
            // 指定的IP地址（由受限类型决定黑白名单）
            List<String> specifiedIp = Arrays.asList(constraintMap.get("ip-list").split(","));
            // 判断是否对IP进行管控
            if (isEnabled) {
                if (restrictionType.equalsIgnoreCase("white")) { //仅允许指定的IP地址访问
                    if (!specifiedIp.contains(IpUtil.getIP(request))) {
                        response.setStatusCode(HttpStatus.UNAUTHORIZED);
                        return customException(response, CustomExceptionEnum.UN_AUTH);
                    }
                } else if (restrictionType.equalsIgnoreCase("black")) { //不允许指定的IP地址访问
                    if (specifiedIp.contains(IpUtil.getIP(request))) {
                        response.setStatusCode(HttpStatus.UNAUTHORIZED);
                        return customException(response, CustomExceptionEnum.UN_AUTH);
                    }
                }
            }
        }
        // 获取请求的URL
        String path = request.getURI().getPath();
        // 判断是否允许放行的URL
        if (commonConfig.getUrl().contains(path)) {
            return chain.filter(exchange);
        }
        // 从头部获取令牌信息
        String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
        // 如果令牌为空，则输出错误代码
        if (StringUtils.isEmpty(token)) {
            // 设置方法不允许被访问，返回405错误代码
            response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
            return customException(response, CustomExceptionEnum.NULL_TOKEN);
        }
        // 解析令牌数据
        try {
            JwtUtil.verify(token);
        } catch (JWTVerificationException e) {
            // 解析失败，返回401错误代码
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return customException(response, CustomExceptionEnum.INVALID);
        }
        // 其他情况都给予放行
        return chain.filter(exchange);
    }

    /***
     * 过滤器执行顺序（越小越先执行）
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 自定义异常
     * @param response
     * @param exceptionEnum
     * @return
     */
    private Mono<Void> customException(ServerHttpResponse response, CustomExceptionEnum exceptionEnum) {
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type","text/json;charset=UTF-8");
        Map<String,Object> map = new HashMap<>();
        map.put("code", exceptionEnum.getCode());
        map.put("msg", exceptionEnum.getMsg());
        byte[] jsonData = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(jsonData);
        return response.writeWith(Mono.just(buffer));
    }
}
